import path from "node:path";
import * as fs from "node:fs";

export class CodeBuilder {
  indent_unit: number = 4;
  #indent_stack: number[] = [];
  #indent: number = 0;
  #content: string = "";

  // utils
  is_empty(): boolean { return this.#content.length == 0; }

  // indent
  #push_indent(count: number = 1): void {
    this.#indent_stack.push(count);
    this.#indent += count;
  }
  #pop_indent(): void {
    if (this.#indent_stack.length == 0) {
      throw new Error("indent stack is empty");
    }
    this.#indent -= this.#indent_stack.pop()!;
  }

  // codegen tools
  $line(code: string): void {
    if (this.#indent == 0) {
      this.#content += `${code}\n`;
    } else {
      const indent_str = " ".repeat(this.#indent * this.indent_unit);
      this.#content += `${indent_str}${code}\n`;
    }
  }
  $indent(content: (b: CodeBuilder) => void): void {
    this.#push_indent();
    content(this);
    this.#pop_indent();
  }
  $indent_n(n: number, content: (b: CodeBuilder) => void): void {
    this.#push_indent(n);
    content(this);
    this.#pop_indent();
  }

  // utils
  $util_header(): void {
    const hint_content = `This file is auto-generated by gen_math, do not edit it manually.`;
    const hint_lines = hint_content.split("\n");
    const max_len = Math.max(...hint_lines.map(line => line.length));
    const border_count = 2;
    const padding_count = 2;
    const line_len = max_len + border_count * 2 + padding_count * 2;
    const fill_line = '*'.repeat(line_len);
    const border = '*'.repeat(border_count);
    const padding = ' '.repeat(padding_count);

    // dump hint
    this.$line(`//! ${fill_line}`)
    for (const line of hint_lines) {
      this.$line(`//! ${border}${padding}${line.padEnd(max_len)}${padding}${border}`)
    }
    this.$line(`//! ${fill_line}`)

    // dump pragma once
    this.$line(``)
    this.$line("#pragma once")
  }

  // to string
  to_string(): string {
    return this.#content;
  }

  // write to file
  write_file(out_path: string) {
    const out_dir = path.dirname(out_path);

    // make dir
    if (!fs.existsSync(out_dir)) {
      fs.mkdirSync(out_dir, { recursive: true });
    }

    // write file
    fs.writeFileSync(out_path, this.to_string(), { encoding: "utf-8" });
  }
}

export interface GlobalBuilders {
  fwd_builder: CodeBuilder
  c_decl_cpp_builder: CodeBuilder
  c_decl_c_builder: CodeBuilder
  traits_builder: CodeBuilder
}

export type ComponentKind = "floating" | "integer" | "boolean";
export const all_component_kinds: ComponentKind[] = ["floating", "integer", "boolean"]
export const dims_all: number[] = [1, 2, 3, 4]
export const dims_no_scalar: number[] = [2, 3, 4]
export const matrix_dims: number[] = [3, 4]

export interface TypeOption {
  component_name: string;
  component_kind: ComponentKind;
  is_signed: boolean;
}

export const type_options: Dict<TypeOption> = {
  "float": {
    component_name: "float",
    component_kind: "floating",
    is_signed: true,
  },
  "double": {
    component_name: "double",
    component_kind: "floating",
    is_signed: true,
  },
  "bool": {
    component_name: "bool",
    component_kind: "boolean",
    is_signed: false,
  },
  "int": {
    component_name: "int32_t",
    component_kind: "integer",
    is_signed: true,
  },
  "uint": {
    component_name: "uint32_t",
    component_kind: "integer",
    is_signed: false,
  },
  "long": {
    component_name: "int64_t",
    component_kind: "integer",
    is_signed: true,
  },
  "ulong": {
    component_name: "uint64_t",
    component_kind: "integer",
    is_signed: false,
  },
}

export interface TypeConvertOption {
  accept_list: string[]
  implicit_list: string[]
}

export const type_convert_options: Dict<TypeConvertOption> = {
  "float": {
    accept_list: ["bool", "int", "uint", "long", "ulong", "double"],
    implicit_list: ["bool", "int", "uint", "long", "ulong"],
  },
  "double": {
    accept_list: ["bool", "int", "uint", "long", "ulong", "float"],
    implicit_list: ["bool", "int", "uint", "long", "ulong", "float"],
  },
  "bool": {
    accept_list: ["int", "uint", "long", "ulong", "float", "double"],
    implicit_list: ["int", "uint", "long", "ulong", "float", "double"],
  },
  "int": {
    accept_list: ["bool", "uint", "long", "ulong", "float", "double"],
    implicit_list: ["bool"],
  },
  "uint": {
    accept_list: ["bool", "int", "long", "ulong", "float", "double"],
    implicit_list: ["bool"],
  },
  "long": {
    accept_list: ["bool", "int", "uint", "ulong", "float", "double"],
    implicit_list: ["bool", "int"],
  },
  "ulong": {
    accept_list: ["bool", "int", "uint", "long", "float", "double"],
    implicit_list: ["bool", "uint"],
  },
}

export function filter_matrix_comp_kind(kind: ComponentKind) {
  if (kind === "floating") {
    return true;
  } else {
    return false
  }
}

export function get_alignas_vector(opt: TypeOption, dim: number): string {
  if (dim === 4 && opt.component_kind === "floating") {
    return `alignas(16) `;
  } else {
    return ``;
  }
}

export function get_alignas_matrix(opt: TypeOption, dim: number): string {
  if (dim === 4 && opt.component_kind === "floating") {
    return `alignas(16) `;
  } else {
    return ``;
  }
}

export function vector_has_simd_optimize(name: string, opt: TypeOption, dim: number): boolean {
  if (opt.component_kind === "floating") {
    switch (name) {
      case "min":
      case "max":
        return true;
      case "abs":
        return true;
      case "rcp":
        return true;
      case "sqrt":
      case "rsqrt":
        return true;
      case "ceil":
      case "floor":
      case "round":
        return true;
      case "cross":
      case "dot":
        return true;
      case "length":
      case "length_squared":
      case "rlength":
        return true;
      case "normalize":
        return true;
      case "lerp":
        return dim > 2;
      case "sin":
      case "asin":
      case "cos":
      case "acos":
      case "tan":
      case "atan":
      case "atan2":
        return true;
      case "sincos":
        return true;
      default:
        return false;
    }
  } else {
    return false;
  }
}